
#include "BfcpTransportIf.hxx"
#include "BfcpMessage.hxx"
#include "BfcpTransactionMessage.hxx"

#include "zklog/zklog_wrapper.h"

using namespace resip;

namespace bfcp
{
    BfcpTransportIf::Exception::Exception(const resip::Data& msg, const resip::Data& file, const int line)
        : resip::BaseException(msg, file, line)
    {
    }

    //////////////////////////////////////////////////////////////////////////
    BfcpTransportIf::BfcpTransportIf(const resip::Data& intfc,
                                     int portNum,
                                     resip::IpVersion version,
                                     resip::Fifo<BfcpTransactionMessage>& rxFifo)
        : mShuttingDown(false)
        , mFd(INVALID_SOCKET)
        , mTuple(intfc, portNum, version, UNKNOWN_TRANSPORT, Data::Empty, Data::Empty)
        , mStateMachineFifo(rxFifo, 8)
        , mTxFifoOutBuffer(mTxFifo)
        , mPollGrp(NULL)
        , mPollItemHandle(NULL)
    {
    }

    BfcpTransportIf::~BfcpTransportIf()
    {
        if (mPollItemHandle)
        {
            mPollGrp->delPollItem(mPollItemHandle);
        }
        if (mFd != INVALID_SOCKET)
        {
            //ZKLOG_DEBUG << "Closing " << mFd;
            closeSocket(mFd);
        }
        mFd = -2;
        if (!mTxFifo.empty())
        {
            ZKLOG_WARNING << "TX Fifo non-empty in ~BfcpTransportIf! Has " << mTxFifo.size() << " messages.";
            while (mTxFifoOutBuffer.messageAvailable())
            {
                BfcpSendData* data = mTxFifoOutBuffer.getNext();
                delete data;
            }
        }
    }

    bool BfcpTransportIf::isFinished() const
    {
        return !mTxFifoOutBuffer.messageAvailable();
    }

    bool BfcpTransportIf::hasDataToSend() const
    {
        return mTxFifoOutBuffer.messageAvailable();
    }

    void BfcpTransportIf::send(std::auto_ptr<BfcpSendData> data)
    {
        mTxFifo.add(data.release());
    }

    void BfcpTransportIf::setPollGrp(resip::FdPollGrp *grp)
    {
        mPollGrp = grp;
    }

    void BfcpTransportIf::pushRxMsgUp(BfcpMessage* msg)
    {
        mStateMachineFifo.add(msg);
    }

    Socket BfcpTransportIf::socket(resip::TransportType type, resip::IpVersion ipVer)
    {
        Socket fd;
        switch (type)
        {
        case UDP:
#ifdef USE_IPV6
            fd = ::socket(ipVer == V4 ? PF_INET : PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
#else
            fd = ::socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
#endif
            break;
        case TCP:
        case TLS:
#ifdef USE_IPV6
            fd = ::socket(ipVer == V4 ? PF_INET : PF_INET6, SOCK_STREAM, 0);
#else
            fd = ::socket(PF_INET, SOCK_STREAM, 0);
#endif
            break;
        default:
            ZKLOG_INFO << "Try to create an unsupported socket type: " << BfcpNetTuple::toData(type);
            resip_assert(0);
            throw BfcpTransportIf::Exception("Unsupported transport", __FILE__, __LINE__);
        }

        if (fd == INVALID_SOCKET)
        {
            int e = getErrno();
            ZKLOG_ERROR << "Failed to create socket: " << strerror(e);
            throw BfcpTransportIf::Exception("Can't create TcpBaseTransport", __FILE__, __LINE__);
        }

#ifdef USE_IPV6
#ifdef __linux__
        int on = 1;
        if (ipVer == V6)
        {
            if (::setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)))
            {
                int e = getErrno();
                InfoLog(<< "Couldn't set sockoptions IPV6_V6ONLY: " << strerror(e));
                error(e);
                throw BfcpTransportIf("Failed setsockopt", __FILE__, __LINE__);
            }
        }
#endif
#endif
        ZKLOG_DEBUG << "Creating fd=" << fd << (ipVer == V4 ? " V4/" : " V6/") << (type == UDP ? "UDP" : "TCP");

        return fd;
    }

    void BfcpTransportIf::bind()
    {

#ifdef USE_NETNS
        ZKLOG_DEBUG << "Binding to " << BfcpNetTuple::inet_ntop(mTuple)
            << " in netns=\"" << mTuple.getNetNs() << "\"";
#else
        ZKLOG_DEBUG << "Binding to " << BfcpNetTuple::inet_ntop(mTuple);
#endif

        if (::bind(mFd, &mTuple.getMutableSockaddr(), mTuple.length()) == SOCKET_ERROR)
        {
            int e = getErrno();
            if (e == EADDRINUSE)
            {
                error(e);
                ZKLOG_ERROR << mTuple << " already in use ";
                throw BfcpTransportIf::Exception("port already in use", __FILE__, __LINE__);
            }
            else
            {
                error(e);
                ZKLOG_ERROR << "Could not bind to " << mTuple;
                throw BfcpTransportIf::Exception("Could not use port", __FILE__, __LINE__);
            }
        }

        // If we bound to port 0, then query OS for assigned port number
        if (mTuple.getPort() == 0)
        {
            socklen_t len = mTuple.length();
            if (::getsockname(mFd, &mTuple.getMutableSockaddr(), &len) == SOCKET_ERROR)
            {
                int e = getErrno();
                ZKLOG_ERROR << "getsockname failed, error=" << e;
                throw BfcpTransportIf::Exception("Could not query port", __FILE__, __LINE__);
            }
        }

//         bool ok = makeSocketNonBlocking(mFd);
//         if (!ok)
//         {
//             ZKLOG_ERROR << "Could not make socket non-blocking " << mTuple.getPort();
//             throw BfcpTransportIf::Exception("Failed making socket non-blocking", __FILE__, __LINE__);
//         }
    }

    void BfcpTransportIf::error(int e)
    {
        switch (e)
        {
        case EAGAIN:
            //ZKLOG_INFO << "No data ready to read" << strerror(e);
            break;
        case EINTR:
            ZKLOG_INFO << "The call was interrupted by a signal before any data was read : " << strerror(e);
            break;
        case EIO:
            ZKLOG_INFO << "I/O error : " << strerror(e);
            break;
        case EBADF:
            ZKLOG_INFO << "fd is not a valid file descriptor or is not open for reading : " << strerror(e);
            break;
        case EINVAL:
            ZKLOG_INFO << "fd is attached to an object which is unsuitable for reading : " << strerror(e);
            break;
        case EFAULT:
            ZKLOG_INFO << "buf is outside your accessible address space : " << strerror(e);
            break;

#if defined(WIN32)
        case WSAENETDOWN:
            ZKLOG_INFO << " The network subsystem has failed.  ";
            break;
        case WSAEFAULT:
            ZKLOG_INFO << " The buf or from parameters are not part of the user address space, "
                "or the fromlen parameter is too small to accommodate the peer address.  ";
            break;
        case WSAEINTR:
            ZKLOG_INFO << " The (blocking) call was canceled through WSACancelBlockingCall.  ";
            break;
        case WSAEINPROGRESS:
            ZKLOG_INFO << " A blocking Windows Sockets 1.1 call is in progress, or the "
                "service provider is still processing a callback function.  ";
            break;
        case WSAEINVAL:
            ZKLOG_INFO << " The socket has not been bound with bind, or an unknown flag was specified, "
                "or MSG_OOB was specified for a socket with SO_OOBINLINE enabled, "
                "or (for byte stream-style sockets only) len was zero or negative.  ";
            break;
        case WSAEISCONN:
            ZKLOG_INFO << "The socket is connected. This function is not permitted with a connected socket, "
                "whether the socket is connection-oriented or connectionless.  ";
            break;
        case WSAENETRESET:
            ZKLOG_INFO << " The connection has been broken due to the keep-alive activity "
                "detecting a failure while the operation was in progress.  ";
            break;
        case WSAENOTSOCK:
            ZKLOG_INFO << "The descriptor is not a socket.  ";
            break;
        case WSAEOPNOTSUPP:
            ZKLOG_INFO << " MSG_OOB was specified, but the socket is not stream-style such as type "
                "SOCK_STREAM, OOB data is not supported in the communication domain associated with this socket, "
                "or the socket is unidirectional and supports only send operations.  ";
            break;
        case WSAESHUTDOWN:
            ZKLOG_INFO << "The socket has been shut down; it is not possible to recvfrom on a socket after "
                "shutdown has been invoked with how set to SD_RECEIVE or SD_BOTH.  ";
            break;
        case WSAEMSGSIZE:
            ZKLOG_INFO << " The message was too large to fit into the specified buffer and was truncated.  ";
            break;
        case WSAETIMEDOUT:
            ZKLOG_INFO << " The connection has been dropped, because of a network failure or because the "
                "system on the other end went down without notice.  ";
            break;
        case WSAECONNRESET:
            ZKLOG_INFO << "Connection reset ";
            break;

        case WSAEWOULDBLOCK:
            ZKLOG_DEBUG << "Would Block ";
            break;

        case WSAEHOSTUNREACH:
            ZKLOG_INFO << "A socket operation was attempted to an unreachable host ";
            break;
        case WSANOTINITIALISED:
            ZKLOG_INFO << "Either the application has not called WSAStartup or WSAStartup failed. "
                "The application may be accessing a socket that the current active task does not own (that is, trying to share a socket between tasks),"
                "or WSACleanup has been called too many times.  ";
            break;
        case WSAEACCES:
            ZKLOG_INFO << "An attempt was made to access a socket in a way forbidden by its access permissions ";
            break;
        case WSAENOBUFS:
            ZKLOG_INFO << "An operation on a socket could not be performed because the system lacked sufficient "
                "buffer space or because a queue was full";
            break;
        case WSAENOTCONN:
            ZKLOG_INFO << "A request to send or receive data was disallowed because the socket is not connected "
                "and (when sending on a datagram socket using sendto) no address was supplied";
            break;
        case WSAECONNABORTED:
            ZKLOG_INFO << "An established connection was aborted by the software in your host computer, possibly "
                "due to a data transmission time-out or protocol error";
            break;
        case WSAEADDRNOTAVAIL:
            ZKLOG_INFO << "The requested address is not valid in its context. This normally results from an attempt to "
                "bind to an address that is not valid for the local computer";
            break;
        case WSAEAFNOSUPPORT:
            ZKLOG_INFO << "An address incompatible with the requested protocol was used";
            break;
        case WSAEDESTADDRREQ:
            ZKLOG_INFO << "A required address was omitted from an operation on a socket";
            break;
        case WSAENETUNREACH:
            ZKLOG_INFO << "A socket operation was attempted to an unreachable network";
            break;

#endif

        default:
            ZKLOG_INFO << "Some other error (" << e << "): " << strerror(e);
            break;
        }
    }

}

